# Copyright (c) 2006 "Gardiner"
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

versionNum = "1.4"

helpStr = 
%q{Usage: winer [OPTION] ... SCORING
Search for vineyard tending sequences based on SCORING

   -h, --help
      show help

   -e, --version
      show version

   -r REQS, --reqs REQS
      require the results to meet the supplied comma separated requirements

   -a FILE, --cuttingFile FILE
      use FILE for the cutting data, defaults to cuttings.txt
 
   -i FILE, --vineyardFile FILE
      use FILE for the vineyard data, defaults to vineyards.txt

   -c CUTTINGS, --cuttings CUTTINGS
      only search for sequences using cuttings matching any regexp listed in CUTTINGS, defaults to all cuttings

   -v VINEYARDS, --vineyards VINEYARDS
      only search for sequences using vineyards matching any regexp listed in VINEYARDS, defaults to all vineyards

   -d, --notCuttings
      only search for sequences using cuttings matching none of the regexps listed in -c

   -f, --notVineyards
      only search for sequences using vineyards matching none of the regexps listed in -f

   -s X, --speed X
      search with speed X between 1 (faster) and 5 (slower), defaults to 3 (incompatible with -g and -p)

   -q, --quiet
      only display the final results

   -n X, --topN X
      show the top X results, defaults to 3

   -u X, --unique X
      only shows unique vineyards in the top results

   -t X, --times X
      do X searches and return the best sequences found, defaults to 3

  Advanced options:

   -g X, --generations X
      use X generations per round, defaults to 20 (incompatible with -s)

   -p X, --population X
     use population size of X, defaults to 300 (incompatible with -s)
}

require 'winer_prog'
require 'getoptlong'
require 'rdoc/usage'

opts = GetoptLong.new(
	[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
	[ '--version', '-e', GetoptLong::NO_ARGUMENT ],
	[ '--reqs', '-r', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--cuttingFile', '-a', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--vineyardFile', '-i', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--cuttings', '-c', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--vineyards', '-v', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--notCuttings', '-d', GetoptLong::NO_ARGUMENT ],
	[ '--notVineyards', '-f', GetoptLong::NO_ARGUMENT ],
	[ '--speed', '-s', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--quiet', '-q', GetoptLong::NO_ARGUMENT ],
	[ '--topN', '-n', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--unique', '-u', GetoptLong::NO_ARGUMENT ],
	[ '--times', '-t', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--generations', '-g', GetoptLong::REQUIRED_ARGUMENT ],
	[ '--population', '-p', GetoptLong::REQUIRED_ARGUMENT ]
	)
	
scoring = ""
reqs = ""
topN = 3
generations = 20
populationSize = 300
roundCount = 3
vineyardFile = "vineyards.txt"
cuttingFile = "cuttings.txt"
vineyardFilter = ""
cuttingFilter = ""
invertVineyards = false
invertCuttings = false
quiet = false
unique = false

opts.each { |opt, arg|
	case opt
		when '--help'
			#RDoc::usage
			print helpStr
			exit 0
			
		when '--version'
			print "Winer version #{versionNum}\nCreated by Gardiner (orgardiner@gmail.com)\n"
			exit 0
			
		when '--reqs'
			reqs = arg
			
		when '--cuttingFile'			
			if !File.exists?(arg)
				print "File not found: #{arg}\n"
				exit 0
			end
			
			cuttingFile = arg
		
		when '--vineyardFile'		
			if !File.exists?(arg)
				print "File not found: #{arg}\n"
				exit 0
			end
			
			vineyardFile = arg
		
		when '--cuttings'
			cuttingFilter = arg
		
		when '--vineyards'
			vineyardFilter = arg
		
		when '--notCuttings'
			invertCuttings = true
		
		when '--notVineyards'
			invertVineyards = true
		
		when '--speed'
			speed = [[1, arg.to_i].max, 5].min
			
			case speed
				when 1
					generations = 10
					populationSize = 100
					
				
				when 2
					generations = 15
					populationSize = 200
				
				
				when 3
					generations = 20
					populationSize = 300
					
				
				when 4
					generations = 25
					populationSize = 400
				
				
				when 5
					generations = 30
					populationSize = 500				
			end
		
		when '--quiet'
			quiet = true
			
		when '--topN'
			topN = arg.to_i
			
		when '--unique'
			unique = true
			
		when '--times'
			roundCount = [arg.to_i, 1].max
			
		when '--generations'
			generations = [arg.to_i, 1].max
		
		when '--population'
			populationSize = [arg.to_i, 1].max
		
	end
}

if ARGV.length < 1
	puts "Missing scoring argument. View --help for more information."
	exit 0
end

# Grab the scoring string from the parameters
scoring = ARGV.shift


w = Winer.new(vineyardFile, cuttingFile)

w.scoring = scoring
w.requirements = reqs


# Filter vineyards and cuttings according to the options given
if vineyardFilter.strip != ""
	begin
		exps = vineyardFilter.split(",").collect {|f| Regexp.new(f.strip) }
	
	rescue RegexpError => ex
		print "Invalid vineyard regular expression: " + ex
		exit 0
	end
	
	quantifier = invertVineyards ? :never : :sometimes
	
	vineyards = w.vineyards.find_all { |v| exps.send(quantifier) { |ex| ex === v.vineyardName } }
	
	if !quiet		
		print "Vineyards: "
		vineyards.but_last.each {|v| print "#{v.vineyardName}, "}
		print "#{vineyards.last.vineyardName}\n"
	end
		
else
	vineyards = w.vineyards
	
	print "Searching all vineyards.\n" unless quiet
end


if cuttingFilter.strip != ""
	begin
		exps = cuttingFilter.split(",").collect {|f| Regexp.new(f.strip) }
	
	rescue RegexpError => ex
		print "Invalid cutting regular expression: " + ex
		exit 0
	end
	
	quantifier = invertCuttings ? :never : :sometimes
	
	strains = w.strains.find_all { |st| exps.send(quantifier) { |ex| ex === st.strainName } }
	
	if !quiet
		print "Cuttings: "
		strains.but_last.each {|v| print "#{v.strainName}, "}
		print "#{strains.last.strainName}\n"
	end
else
	strains = w.strains
	
	print "Searching all cuttings.\n" unless quiet
end	

if !quiet	
	print "#{roundCount} round#{roundCount > 1 ? "s" : ""}. Beginning search...\nRound  1:    0%"
end


t = Time.now

best = []

roundCount.times {|r|	
	printf 8.chr*15 + "Round %2d:    0%", r+1 unless quiet
	
	w.initialize_ea(vineyards, strains, populationSize)
	w.iterate(generations, quiet)
	
	if unique
		best.concat(w.get_best_n(topN) { |a, b| 
			vineyards[a[1].vineyardIndex].vineyardName == vineyards[b[1].vineyardIndex].vineyardName 
		})
	else
		best.concat(w.get_best_n(topN))
	end
}

print "\nSearch complete in #{Time.now - t} seconds.\n" unless quiet

best = best.sort{|a,b| b[0] <=> a[0]}

if unique
	uniqueBest = []
	
	while uniqueBest.length < topN && best.length > 0 do
		uniqueBest << best.first
		best.delete_if {|i| vineyards[uniqueBest.last[1].vineyardIndex].vineyardName == vineyards[i[1].vineyardIndex].vineyardName }
	end
	
	best = uniqueBest
else
	best = best[0..topN-1]
end

best.each { |i| 
	print "#{i[1]}\t\t\t\t"
	printf "Score: %f\n", i[0] 
}

